﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;
using Microsoft.WindowsAzure.ServiceRuntime;
using Microsoft.Web.Administration;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using System.Web;
using Ionic.Zip;
using smarx.WazStorageExtensions;

namespace com.dynamicdeploy.provisioningutils
{

    public static class PluginCalls
    {
        const string LOCAL_WEB_CONFIG_NAME = "ReplaceWebConfig.{0}";
        const int MAX_RETRIES_FOR_WEB_CONFIG = 3;
        const string DD_TEMP_STORAGE_NAME = "dynamicdeploytempstorage";

        public static readonly RetryPolicy RETRY_POLICY = RetryPolicies.Retry(4, TimeSpan.Zero);
        #region replace web.config
        public static void SetConfigurationPublisher()
        {
            try
            {

                SetupConfigurationSettingPublisher();
                Trace.WriteLine(string.Format("{0}\t{1}", DateTime.Now.ToString(), "SetConfigurationPublisher succeeded."));
            }
            catch (Exception ex)
            {
                Trace.WriteLine("Error setting configuration publisher " + ex.Message);

            }


        }
        public static void ReplaceWebConfigFile()
        {
            DefaultTraceListener drl;
            drl = (DefaultTraceListener)Trace.Listeners["Default"];
            drl.LogFileName = "ReplaceWebConfig.log";

            SetConfigurationPublisher();
            Trace.WriteLine(string.Format("{0}\t{1}", DateTime.Now.ToString(), "Start"));
          
            int count = 0;
            while (count <= MAX_RETRIES_FOR_WEB_CONFIG)
            {
                count++;

                Trace.WriteLine("Retry Count:" + count);
                try
                {
                    if (!RoleEnvironment.IsAvailable)
                    {
                        Trace.WriteLine(string.Format("{0}\t{1}", DateTime.Now.ToString(), "RoleEnvironment.IsAvailable is false."));
                        System.Threading.Thread.Sleep(30000);
                        continue;
                    }
                    int WaitInterval = 60000;//int.Parse(RoleEnvironment.GetConfigurationSettingValue("com.dynamicdeploy.replacewebconfig.WaitInterval"));
                    string containerName = RoleEnvironment.GetConfigurationSettingValue("com.dynamicdeploy.replacewebconfig.ContainerName");

                    string blobName = RoleEnvironment.GetConfigurationSettingValue("com.dynamicdeploy.replacewebconfig.BlobName");
                    string storageSettingName = RoleEnvironment.GetConfigurationSettingValue("com.dynamicdeploy.replacewebconfig.StorageAccountSettingName");
                    string targetWebServiceModel = RoleEnvironment.GetConfigurationSettingValue("com.dynamicdeploy.replacewebconfig.SiteNameFromServiceModel");
                    string localRelativePathToWebConfig = RoleEnvironment.GetConfigurationSettingValue("com.dynamicdeploy.replacewebconfig.LocalRelativePathToWebConfig");

                    if (localRelativePathToWebConfig.StartsWith("\\"))
                    {

                        localRelativePathToWebConfig = localRelativePathToWebConfig.Substring(1);
                    }

                    using (var server = new ServerManager())
                    {
                        var siteName = string.Format("{0}_{1}", RoleEnvironment.CurrentRoleInstance.Id, targetWebServiceModel);
                        var site = server.Sites[siteName];
                        if (site == null)
                        {
                            Trace.WriteLine(string.Format("{0}\t{1}", DateTime.Now.ToString(), "IIS Virtual site does not configure."));
                            System.Threading.Thread.Sleep(WaitInterval);
                            continue;
                        }
                        Configuration siteConfig = site.GetWebConfiguration();

                        string newWebConfig = null;
                        string localFileName = string.Format(LOCAL_WEB_CONFIG_NAME, blobName);
                        if (!File.Exists(localFileName))
                        {
                            //Get the web.config from the storage account
                            CloudBlobClient bc = CloudStorageAccount.FromConfigurationSetting(storageSettingName).CreateCloudBlobClient();
                            CloudBlobContainer container = bc.GetContainerReference(containerName);
                            CloudBlob b = container.GetBlobReference(blobName);

                            b.DownloadToFile(localFileName);
                            Trace.WriteLine(string.Format("{0}\t{1}{2}", DateTime.Now.ToString(), "Downloaded file to ", localFileName));


                        }
                        newWebConfig = File.ReadAllText(localFileName);


                        //Replace

                        Site sc = server.Sites[siteName];
                        Application a = sc.Applications[0];
                        VirtualDirectory v = a.VirtualDirectories[0];
                        string cp = v.PhysicalPath;

                        if (!string.IsNullOrEmpty(localRelativePathToWebConfig))
                        {
                            cp = Path.Combine(cp, localRelativePathToWebConfig);

                        }

                        string wcp = Path.Combine(cp, "web.config");
                        string webConfig = File.ReadAllText(wcp);

                        if (newWebConfig == webConfig)
                        {
                            Trace.WriteLine(string.Format("{0}\t{1}", DateTime.Now.ToString(), "Complete."));
                            return;
                        }
                        else
                        {

                            Trace.WriteLine(string.Format("{0}\t{1}", DateTime.Now.ToString(), "Files are different, replacing..."));
                        }


                        File.WriteAllText(wcp, newWebConfig);
                        Trace.WriteLine(string.Format("{0}\t{1}", DateTime.Now.ToString(), "Replaced new web.config"));
                        Trace.WriteLine(string.Format("{0}\t{1}", DateTime.Now.ToString(), "Stopping Site."));
                        sc.Stop();
                        sc.Start();
                        Trace.WriteLine(string.Format("{0}\t{1}", DateTime.Now.ToString(), "Started Site."));


                    }
                }
                catch (Exception ex)
                {
                    Trace.WriteLine(string.Format("{0}\t{1}{2}", DateTime.Now.ToString(), "Exception:", ex.ToString()));
                    System.Threading.Thread.Sleep(30000);
                    continue;
                }
            }
        }

        #endregion


        #region replace any file
        public static void ReplaceAnyConfigFile()
        {
            DefaultTraceListener drl;
            drl = (DefaultTraceListener)Trace.Listeners["Default"];
            drl.LogFileName = "DDFileMaster.log";

            Trace.WriteLine(string.Format("{0}\t{1}", DateTime.Now.ToString(), "Starting DDFileMaster"));
            try
            {

                SetupConfigurationSettingPublisher();
                Trace.WriteLine(string.Format("{0}\t{1}", DateTime.Now.ToString(), "SetConfigurationPublisher succeeded."));
            }
            catch (Exception ex)
            {
                Trace.WriteLine("Error setting configuration publisher " + ex.Message);

            }

            int count = 0;
            while (count <= MAX_RETRIES_FOR_WEB_CONFIG)
            {
                count++;

                Trace.WriteLine("Retry Count:" + count);
                try
                {
                    if (!RoleEnvironment.IsAvailable)
                    {
                        Trace.WriteLine(string.Format("{0}\t{1}", DateTime.Now.ToString(), "RoleEnvironment.IsAvailable is false."));
                        System.Threading.Thread.Sleep(30000);
                        continue;
                    }
                    int WaitInterval = 60000;//int.Parse(RoleEnvironment.GetConfigurationSettingValue("com.dynamicdeploy.replacewebconfig.WaitInterval"));
                    string provContainerName = RoleEnvironment.GetConfigurationSettingValue("com.dynamicdeploy.plugins.replaceconfigfiles.ProvisioningHive");

                    string packageContainerName = RoleEnvironment.GetConfigurationSettingValue("com.dynamicdeploy.plugins.replaceconfigfiles.PackageHive");


                    string storageSettingName = RoleEnvironment.GetConfigurationSettingValue("com.dynamicdeploy.plugins.replaceconfigfiles.StorageAccountSettingName");
                    string targetWebServiceModel = RoleEnvironment.GetConfigurationSettingValue("com.dynamicdeploy.plugins.replaceconfigfiles.SiteNameFromServiceModel");



                    using (var server = new ServerManager())
                    {
                        var siteName = string.Format("{0}_{1}", RoleEnvironment.CurrentRoleInstance.Id, targetWebServiceModel);
                        var site = server.Sites[siteName];
                        if (site == null)
                        {
                            Trace.WriteLine(string.Format("{0}\t{1}", DateTime.Now.ToString(), "IIS Virtual site does not configure."));
                            System.Threading.Thread.Sleep(WaitInterval);
                            continue;
                        }
                        Configuration siteConfig = site.GetWebConfiguration();
                        Site sc = server.Sites[siteName];
                        Application a = sc.Applications[0];
                        VirtualDirectory v = a.VirtualDirectories[0];
                        string webRootPath = v.PhysicalPath;

                        //Get the config from the storage account
                        CloudBlobClient blobClient = CloudStorageAccount.FromConfigurationSetting(storageSettingName).CreateCloudBlobClient();

                        bool configReplace = false, zipReplace = false;
                       configReplace = ReplaceLocalFilesWithBlobs(webRootPath, provContainerName, blobClient);

                       zipReplace = ReplaceLocalFilesWithBlobs(webRootPath, packageContainerName, blobClient);

                       if (configReplace && zipReplace)
                       {

                           Trace.WriteLine(string.Format("{0}\t{1}", DateTime.Now.ToString(), "Stopping Site."));
                           sc.Stop();
                           sc.Start();
                           Trace.WriteLine(string.Format("{0}\t{1}", DateTime.Now.ToString(), "Started Site."));

                           return;
                       }

                     

                    }
                }
                catch (Exception ex)
                {
                    Trace.WriteLine(string.Format("{0}\t{1}{2}", DateTime.Now.ToString(), "Exception:", ex.ToString()));
                    System.Threading.Thread.Sleep(30000);
                    continue;
                }
            }
        }
        #endregion

        #region Utility functions


        private static void SetupConfigurationSettingPublisher()
        {
            // This code sets up a handler to update CloudStorageAccount instances when their corresponding
            // configuration settings change in the service configuration file.
            Trace.WriteLine("Setting up configuration setting publishing");
            CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) =>
            {
                // Provide the configSetter with the initial value
                configSetter(RoleEnvironment.GetConfigurationSettingValue(configName));


            });
        }

        private static void AddElement(ConfigurationElementCollection appSettings, string key, string value)
        {
            if (appSettings.Any(t => t.GetAttributeValue("key").ToString() == key))
            {
                appSettings.Remove(appSettings.First(t => t.GetAttributeValue("key").ToString() == key));
            }

            ConfigurationElement addElement = appSettings.CreateElement("add");
            addElement["key"] = key;
            addElement["value"] = value;
            appSettings.Add(addElement);
        }

        private static void ReplaceTextFile(string localTempFilePath, string finalDestinationPath)
        {
            bool replace = false;
               string newFileContent = File.ReadAllText(localTempFilePath);

               if (!string.IsNullOrEmpty(newFileContent))
               {
                   string oldFileContent = null;

                   if (File.Exists(finalDestinationPath))
                   {
                       //read the old file
                       oldFileContent = File.ReadAllText(finalDestinationPath);

                       if (oldFileContent != newFileContent)
                       {
                           replace = true;
                           Trace.WriteLine(string.Format("{0}\t{1}{2}", DateTime.Now.ToString(), "Files differ. Will try to replace.", finalDestinationPath));
                       }
                       else
                       {
                           Trace.WriteLine(string.Format("{0}\t{1}{2}", DateTime.Now.ToString(), "Files are same. Will not replace.", finalDestinationPath));

                       }
                   }
                   else
                   {
                       replace = true;

                   }

               }
               else
               {
                   Trace.WriteLine(string.Format("{0}\t{1}{2}", DateTime.Now.ToString(), "We do not replace blank content. Blank content found in file.", localTempFilePath));

               }

               if (replace)
               {
                   Trace.WriteLine(string.Format("{0}\t Replaced files {1} to {2}", DateTime.Now.ToString(), localTempFilePath, finalDestinationPath));
                   File.WriteAllText(finalDestinationPath, newFileContent);

                   Trace.WriteLine(string.Format("{0}\t Replaced files {1} to {2}", DateTime.Now.ToString(), localTempFilePath, finalDestinationPath));
               }

               Trace.WriteLine(string.Format("{0}\t{1}{2}", DateTime.Now.ToString(), "Complete.", finalDestinationPath));

        }
        private static bool ReplaceLocalFilesWithBlobs(string webRootPath, string containerName, CloudBlobClient blobClient)
        {
            bool ret = true;
            string localTempStoragePath = Path.Combine(Utils.GetLocalStorageRootPath(DD_TEMP_STORAGE_NAME), containerName);
           // CloudBlobDirectory dir = blobClient.GetBlobDirectoryReference(containerName + "/");

            if (!Directory.Exists(localTempStoragePath))
            {
                try
                {
                    Directory.CreateDirectory(localTempStoragePath);

                }
                catch (Exception)
                {
                    throw;

                }

            }

          CloudBlobContainer dir =  blobClient.GetContainerReference(containerName);

            var blobItems = dir.ListBlobs(new BlobRequestOptions()
            {
                UseFlatBlobListing = true,
                Timeout = TimeSpan.FromSeconds(60),
                RetryPolicy = RETRY_POLICY
            });

            foreach(IListBlobItem blobItem in blobItems)
            {
                string finalPath = "";
                
                try
                {
                    CloudBlob bl = blobClient.GetBlobReference(blobItem.Uri.AbsoluteUri);
                    bl.FetchAttributes();

                    if (bl.Metadata["runonceperdeployment"] == "1")
                    {
                        if (bl.Metadata["installStatus"] == "0")
                        {
                            bl.Metadata["installStatus"] = "1";
                            bl.SetMetadata();

                            using (var arl = new AutoRenewLease(bl))
                            {
                                if (arl.HasLease)
                                {
                                    Trace.WriteLine(string.Format("{0}\t{1}{2}", DateTime.Now.ToString(), "Acquired exclusive access to ", bl.Uri.AbsoluteUri));
                                    RunTheBlobReplaceAndExtractionJob(blobClient, bl, finalPath, webRootPath, localTempStoragePath);

                                  

                                }


                            }
                            //TODO: THis can introduce race condition as the access is not exclusive outside of the block.
                            //SOlution: Take the source of AutoRenewLease, export leaseId as public and then pass it in SetMetadata()
                            bl.Metadata["installStatus"] = "2";
                            bl.SetMetadata();
                            Trace.WriteLine(string.Format("{0}\t{1}{2}", DateTime.Now.ToString(), "Executed runonce function on blob ", bl.Uri.AbsoluteUri));
                        }
                    }
                    else
                    {

                        RunTheBlobReplaceAndExtractionJob(blobClient, bl, finalPath, webRootPath, localTempStoragePath);

                    }
                       

                  

                }
                catch (Exception ey)
                {
                    Trace.WriteLine(string.Format("{0}\t{1}{2}. Exception:{3}", DateTime.Now.ToString(), "Error downloading file to ", finalPath, ey.Message));
                    ret = false;
                }

            };

            return ret;
        }


        private static void RunTheBlobReplaceAndExtractionJob(CloudBlobClient blobClient, CloudBlob bl, string finalPath, string webRootPath, string localTempStoragePath)
        {
            string final_destination = bl.Metadata["final_destination"];
            string blobName = Utils.GetFileNameFromUrl(bl.Uri.AbsoluteUri);

            if (!string.IsNullOrEmpty(final_destination))
            {
                final_destination = HttpUtility.UrlDecode(final_destination);
                string final_destination_name = bl.Metadata["final_destination_name"];

                if (string.IsNullOrEmpty(final_destination_name))
                {

                    // final_destination_name = bl.Name;

                    final_destination_name = blobName;
                }

                final_destination_name = HttpUtility.UrlDecode(final_destination_name);
                //if the final destination is specified, then copy the file there
                if (final_destination == ".")
                {
                    finalPath = Path.Combine(webRootPath, final_destination_name);

                }

                else
                {

                    finalPath = Path.Combine(webRootPath, final_destination, final_destination_name);

                }


                //1. First download locally
                string localTempFilePath = Path.Combine(localTempStoragePath, blobName);
                bl.DownloadToFile(localTempFilePath);

                Trace.WriteLine(string.Format("{0}\t{1}{2}", DateTime.Now.ToString(), "Downloaded file to temp folder ", localTempFilePath));





                //Check to see if we have to extract it

                string extract = bl.Metadata["extract"];

                if (!string.IsNullOrEmpty(extract) && extract == "1")
                {


                    string localDir = Path.Combine(localTempStoragePath, Path.GetFileNameWithoutExtension(blobName));
                    Trace.WriteLine(string.Format("{0}\t{1}{2}", DateTime.Now.ToString(), "Extracting files to ", localDir));
                    ExtractFiles(localDir, localTempFilePath);
                    Trace.WriteLine(string.Format("{0}\t{1}{2}", DateTime.Now.ToString(), "Extracted files to ", localDir));


                    if (final_destination == "cloud")
                    {
                        //Since you cannot have nested containers, if a base container is specified, use it and upload everything
                        //otherwise, each folder in the directory will map to a container
                        string extractContainerName = null;
                        if (!string.IsNullOrEmpty(bl.Metadata["containerName"]))
                        {

                            extractContainerName = HttpUtility.UrlDecode(bl.Metadata["containerName"]);
                            Trace.WriteLine(string.Format("{0}\t Uploading directory {1} to container {2}", DateTime.Now.ToString(), localDir, extractContainerName));

                            CloudBlobContainer c = blobClient.GetContainerReference(extractContainerName);
                            c.CreateIfNotExist();
                            Utils.UploadDirectoryRecursive(localDir, c);
                            Trace.WriteLine(string.Format("{0}\t Uploaded directory {1} to container {2}", DateTime.Now.ToString(), localDir, extractContainerName));
                        }
                        else
                        {
                            //copy the directories

                            string[] directories = Directory.GetDirectories(localDir);

                            foreach (string d in directories)
                            {
                                Trace.WriteLine(string.Format("{0}\t Uploading directory {1} to container {2}", DateTime.Now.ToString(), d, extractContainerName));

                                extractContainerName = Path.GetFileName(d);

                                //Create a container for each directory and then upload for each
                                CloudBlobContainer c = blobClient.GetContainerReference(extractContainerName);
                                c.CreateIfNotExist();
                                Utils.UploadDirectoryRecursive(d, c);
                                Trace.WriteLine(string.Format("{0}\t Uploaded directory {1} to container {2}", DateTime.Now.ToString(), d, extractContainerName));
                            }

                        }

                    }
                }
                else
                {

                    ReplaceTextFile(localTempFilePath, finalPath);

                }



            }

        }

        private static void ExtractFiles(string localFolder, string zipFile)
        {

            using (ZipFile zip1 = ZipFile.Read(Path.Combine(localFolder, zipFile)))
            {

                foreach (ZipEntry e in zip1)
                {
                    e.Extract(localFolder, ExtractExistingFileAction.OverwriteSilently);
                }
            }
        }

      
       
        #endregion
    }
}
